#include <IntroState.h>
#include <PlayState.h>

template<> IntroState* Ogre::Singleton<IntroState>::ms_Singleton = 0;

void IntroState::enter()
{
  _root = Ogre::Root::getSingletonPtr();

  try {
    _sceneMgr = _root->createSceneManager(Ogre::ST_GENERIC, "SceneManager"); }

  catch (Ogre::ItemIdentityException e) {
    _sceneMgr = _root->getSceneManager("SceneManager"); }


  // Cursor
  _overlayMgr = Ogre::OverlayManager::getSingletonPtr();
  _overlayMgr->getByName("cursor")->show();

  // Audio
  _pTrackManager = new TrackManager;
  _pSoundFXManager = new SoundFXManager;

  _pTrackManager->load("soviet_anthem.ogg")->play();

  // Ray
  _raySceneQuery = _sceneMgr->createRayQuery(Ogre::Ray());

  // Records
  _pRecordMgr = RecordManager::getRecordManager();

  // Variables
  _exitGame = false;
  _menuState = MENU;

  // Camara
  Ogre::SceneNode* cameraNode = buildCamera();
  _sceneMgr->getRootSceneNode()->addChild(cameraNode);

  // Sombras
  setSceneShadow();

  // Escenario
  Ogre::SceneNode* stage = buildStage();
  _sceneMgr->getRootSceneNode()->addChild(stage);

  // Menu
  Ogre::SceneNode* menu = buildMenu();
  cameraNode->addChild(menu);
}

void IntroState::exit()
{
  // Audio
  _pTrackManager->load("soviet_anthem.ogg")->stop();
  delete _pSoundFXManager;
  delete _pTrackManager;

  _overlayMgr->getByName("cursor")->hide();
  _sceneMgr->destroyQuery(_raySceneQuery);
  _sceneMgr->clearScene();
  _root->getAutoCreatedWindow()->removeAllViewports();
}

void IntroState::pause() {
  this->exit(); }

void IntroState::resume() {
  this->enter(); }

bool IntroState::frameStarted(const Ogre::FrameEvent& evt) 
{
  Ogre::Real deltaT = evt.timeSinceLastFrame;

  _sceneMgr->getSceneNode("MainCamera")->yaw(Ogre::Degree(deltaT * 10));
  
  return true;
}

bool IntroState::frameEnded(const Ogre::FrameEvent& evt) {
  return not _exitGame; }

void IntroState::keyPressed(const OIS::KeyEvent& e)
{
  if (e.key == OIS::KC_ESCAPE)
    _exitGame = true;
}

void IntroState::keyReleased(const OIS::KeyEvent& e ) {}

void IntroState::mouseMoved(const OIS::MouseEvent& e)
{
  // Movimiento del raton
  Ogre::OverlayElement* cursor = _overlayMgr->getOverlayElement("cursor");
  cursor->setLeft(e.state.X.abs);
  cursor->setTop(e.state.Y.abs);
}

void IntroState::mousePressed(const OIS::MouseEvent& e, OIS::MouseButtonID id) {}

void IntroState::mouseReleased(const OIS::MouseEvent& e, OIS::MouseButtonID id)
{
  if (_menuState == MENU)
  {
    int posx = e.state.X.abs;
    int posy = e.state.Y.abs;

//    Ogre::Ray r = setRayQuery(posx, posy, _QUERY_MASK_MENU_);
    setRayQuery(posx, posy, _QUERY_MASK_MENU_);
    Ogre::RaySceneQueryResult& result = _raySceneQuery->execute();
    Ogre::RaySceneQueryResult::iterator it;

    it = result.begin();

    if (it != result.end())
    {
      if (it->movable->getName() == "OptionPlay")
        pushState(PlayState::getSingletonPtr());

      else
        if (it->movable->getName() == "OptionRecords")
        {
          _menuState = RECORDS;
          _sceneMgr->getSceneNode("Options")->setVisible(false);
          _sceneMgr->getSceneNode("Records")->setVisible(true);

        } else {
          if (it->movable->getName() == "OptionCredits")
          {
            _menuState = CREDITS;
            _sceneMgr->getSceneNode("Options")->setVisible(false);
            _sceneMgr->getSceneNode("Credits")->setVisible(true);
          }
          else if (it->movable->getName() == "OptionExit")
            _exitGame = true;
        }
    }

  } else {

    _menuState = MENU;
    _sceneMgr->getSceneNode("Records")->setVisible(false);
    _sceneMgr->getSceneNode("Credits")->setVisible(false);
    _sceneMgr->getSceneNode("Options")->setVisible(true);
  }
}

Ogre::Ray IntroState::setRayQuery(int posx, int posy, Ogre::uint32 mask) {
  Ogre::Ray rayMouse = _camera->getCameraToViewportRay
    (posx/float(_root->getAutoCreatedWindow()->getWidth()), 
     posy/float(_root->getAutoCreatedWindow()->getHeight()));
  _raySceneQuery->setRay(rayMouse);
  _raySceneQuery->setSortByDistance(true);
  _raySceneQuery->setQueryMask(mask);
  return (rayMouse);
}

IntroState* IntroState::getSingletonPtr()
{
  return ms_Singleton;
}

IntroState& IntroState::getSingleton()
{ 
  assert(ms_Singleton);
  return *ms_Singleton;
}

Ogre::SceneNode*
IntroState::buildCamera() {

  Ogre::SceneNode* cameraNode = _sceneMgr->createSceneNode("MainCamera");

  try {
    _camera = _sceneMgr->createCamera("MainCamera");
  }
  catch (Ogre::ItemIdentityException e) {
    _camera = _sceneMgr->getCamera("MainCamera");
  }

  _viewport = _root->getAutoCreatedWindow()->addViewport(_camera);
  _viewport->setBackgroundColour(Ogre::ColourValue(1.0, 1.0, 1.0));	

  _camera->setNearClipDistance(5);
  _camera->setFarClipDistance(10000);

  Ogre::SceneNode* cameraPosition = _sceneMgr->createSceneNode();
  cameraPosition->attachObject(_camera);
  cameraPosition->setPosition(Ogre::Vector3(25, 2.5, 80));
  cameraPosition->pitch(Ogre::Degree(20));

  cameraNode->addChild(cameraPosition);

  return cameraNode;
}

void
IntroState::setSceneShadow() {

  // Sombras
  _sceneMgr->setShadowTechnique(Ogre::SHADOWTYPE_TEXTURE_MODULATIVE); // Tecnica
  _sceneMgr->setShadowColour(Ogre::ColourValue(0.3, 0.3, 0.3)); // Color
  _sceneMgr->setAmbientLight(Ogre::ColourValue(0.6, 0.6, 0.6)); // Luz ambiente
  _sceneMgr->setShadowTextureCount(1); // Buffers de textura
  _sceneMgr->setShadowTextureSize(1024); // Tamanyo textura
}

Ogre::Light*
IntroState::buildStageLight() {

  // Iluminacion
  Ogre::Light* sun = _sceneMgr->createLight("Sun");

  sun->setType(Ogre::Light::LT_DIRECTIONAL); // Tipo
  sun->setDirection(Ogre::Vector3(0.5, -0.5, -0.5)); // Direccion 
  sun->setDiffuseColour(Ogre::ColourValue::White); // Color difuso
  sun->setSpecularColour(Ogre::ColourValue(0.5, 0.5, 0.5)); // Color especular
  sun->setLightMask(_LIGHT_MASK_STAGE_); // Mascara
  sun->setCastShadows(true); // Sombras

  return sun;
}

Ogre::SceneNode*
IntroState::buildStage() {

  // Nodo del escenario
  Ogre::SceneNode* stage = _sceneMgr->createSceneNode("Stage");

  // Cielo
  _sceneMgr->setSkyBox(true, "skyBox");

  // Iluminacion
  Ogre::Light* menuLight = buildStageLight();
  stage->attachObject(menuLight);

  // Catedral
  Ogre::Entity* cathedralEnt = 
    _sceneMgr->createEntity("Cathedral", "cathedral.mesh");

  cathedralEnt->setQueryFlags(_QUERY_MASK_STAGE_);
  cathedralEnt->setLightMask(_LIGHT_MASK_STAGE_);
  cathedralEnt->setCastShadows(true);

  stage->attachObject(cathedralEnt);

  // Plano del suelo
  Ogre::Plane floor(Ogre::Vector3::UNIT_Y, 0);
  Ogre::MeshManager::getSingleton().createPlane("FloorPlane",
      Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, floor,
      500,500,1,1,true,1,50,50,Ogre::Vector3::UNIT_Z);

  // Entidad del suelo
  Ogre::Entity* floorEnt = _sceneMgr->createEntity("Floor", "FloorPlane");

  floorEnt->setMaterialName("Floor");
  floorEnt->setQueryFlags(_QUERY_MASK_STAGE_);
  floorEnt->setLightMask(_LIGHT_MASK_STAGE_);
  floorEnt->setCastShadows(false);

  stage->attachObject(floorEnt);

  // Lluvia de tetrominos
  Ogre::SceneNode *tetrominoRain = _sceneMgr->createSceneNode("TetrominoRain");

  char tetrominoName[] = {'I', 'J', 'L', 'O', 'S', 'T', 'Z'};
  for (int i = 0; i < 7; i++) {

    // Obtener nombre del sistema de particulas
    std::ostringstream psNameStr, matNameStr; 	
    psNameStr << "tetrominoRain" << tetrominoName[i];

    // Sistema de particulas
    Ogre::ParticleSystem* tetrominoRainPs = 
      _sceneMgr->createParticleSystem(psNameStr.str(), "tetrominoRain");

    // Obtener nombre del material del sistema de particulas
    matNameStr << "tetromino" << tetrominoName[i];

    // Fijar material del sistema de particulas
    tetrominoRainPs->setMaterialName(matNameStr.str());

    tetrominoRain->attachObject(tetrominoRainPs);
  }

  tetrominoRain->translate(0, 150, 0);
  tetrominoRain->scale(3, 0.1, 3);

  stage->addChild(tetrominoRain);

  return stage;
}

Ogre::Light*
IntroState::buildMenuLight() {

  // Iluminacion menu
  Ogre::Light *menuLight = _sceneMgr->createLight("MenuLight");

  menuLight->setType(Ogre::Light::LT_DIRECTIONAL); // Tipo
  menuLight->setDirection(Ogre::Vector3(0, 0, -1)); // Direccion
  menuLight->
    setDiffuseColour(Ogre::ColourValue(0.7, 0.7, 0.7)); // Color difuso
  menuLight->
    setSpecularColour(Ogre::ColourValue(0.3, 0.3, 0.3)); // Color especular
  menuLight->setLightMask(_LIGHT_MASK_MENU_); // Mascara
  menuLight->setCastShadows(false); // Sombras

  return menuLight;
}

Ogre::SceneNode*
IntroState::buildMenu() {

  // Nodo del menu
  Ogre::SceneNode* menu = _sceneMgr->createSceneNode("Menu");

  // Iluminacion	
  Ogre::Light* menuLight = buildMenuLight();	
  menu->attachObject(menuLight);

  // Opciones
  Ogre::SceneNode* options = buildOptions();
  menu->addChild(options);	

  // Records
  Ogre::SceneNode* records = buildRecords();
  records->setVisible(false);
  records->translate(Ogre::Vector3(-40, 35, 10));

  menu->addChild(records);	

  // Creditos
  Ogre::SceneNode* credits = buildCredits();
  credits->setVisible(false);
  menu->addChild(credits);

  menu->translate(Ogre::Vector3(60, 10, 10));
  menu->pitch(Ogre::Degree(20));

  return menu;
}

Ogre::SceneNode*
IntroState::buildOptions() {

  // Nodo de las opciones
  Ogre::SceneNode* options = _sceneMgr->createSceneNode("Options");

  // Opciones
  std::vector<std::string> optionName(5);
  optionName.at(0) = "OptionTitle";
  optionName.at(1) = "OptionPlay";
  optionName.at(2) = "OptionRecords";
  optionName.at(3) = "OptionCredits";
  optionName.at(4) = "OptionExit";

  // Recorrer opciones
  std::vector<std::string>::iterator optionIt; 	
  for (optionIt = optionName.begin();
      optionIt != optionName.end();
      optionIt++)
  {

    // Nombre de la mesh de la opcion
    std::ostringstream meshNameStr;
    meshNameStr << *optionIt << ".mesh";

    // Entidad de la opcion
    Ogre::Entity* optionEnt = 
      _sceneMgr->createEntity(*optionIt, meshNameStr.str());

    optionEnt->setQueryFlags(_QUERY_MASK_MENU_);
    optionEnt->setLightMask(_LIGHT_MASK_MENU_);
    optionEnt->setCastShadows(false);

    // Nodo de la opcion
    Ogre::SceneNode* optionNode = _sceneMgr->createSceneNode(*optionIt);

    optionNode->attachObject(optionEnt);
    options->addChild(optionNode);
  }

  return options;
}

Ogre::SceneNode*
IntroState::buildRecords() {

  int posx, posy;

  // Obtener vector de records
  _pRecordMgr->readRecords(RECORDFILE);
  std::vector<Record>* recordVec = _pRecordMgr->getRecords();

  // Crear nodo para los records
  Ogre::SceneNode* records = _sceneMgr->createSceneNode("Records");

  // Recorrer vector de records	
  std::vector<Record>::iterator recordIt;
  for (recordIt = recordVec->begin(), posy = 0;
      recordIt != recordVec->end(); 
      recordIt++, posy -= 6)
  {

    // Crear nodo para el record
    Ogre::SceneNode* recordNode = _sceneMgr->createSceneNode();

    // Crear string para cada record
    std::ostringstream recordStrStream;
    recordStrStream << recordIt->name << " " << recordIt->score;

    std::string recordStr;
    recordStr = recordStrStream.str();

    // Recorrer string del record
    std::string::iterator recordStrIt;
    for (recordStrIt = recordStr.begin(), posx = 0;
        recordStrIt != recordStr.end();
        recordStrIt++, posx += 4)
    {

      if (*recordStrIt == ' ' || *recordStrIt == '\n') continue;

      // Crear string con el nombre del mesh
      std::ostringstream letterStrStream;

      letterStrStream << "LETTER_" << *recordStrIt << ".mesh";

      // Crear entidad con la mesh
      Ogre::Entity* letterEnt = _sceneMgr->createEntity(letterStrStream.str());

      letterEnt->setQueryFlags(_QUERY_MASK_STAGE_);
      letterEnt->setLightMask(_LIGHT_MASK_MENU_);
      letterEnt->setCastShadows(false);

      // Crear nodo para la letra
      Ogre::SceneNode* letterNode = _sceneMgr->createSceneNode();

      letterNode->attachObject(letterEnt);
      letterNode->translate(Ogre::Vector3(posx, 0, 0));
      recordNode->addChild(letterNode);
    }

    recordNode->translate(Ogre::Vector3(0, posy, 0));
    records->addChild(recordNode);
  }

  return records;
}

Ogre::SceneNode*
IntroState::buildCredits() {

  // Entidad de los creditos
  Ogre::Entity* creditsEnt = _sceneMgr->createEntity("Credits", "Credits.mesh");
  creditsEnt->setQueryFlags(_QUERY_MASK_STAGE_);
  creditsEnt->setLightMask(_LIGHT_MASK_MENU_);
  creditsEnt->setCastShadows(false);

  // Nodo de los creditos
  Ogre::SceneNode* credits = _sceneMgr->createSceneNode("Credits");
  credits->attachObject(creditsEnt);

  return credits;
}

